#import "JavascriptForm.h"
#import "FormView.h"
#import "FormEditor.h"
#import "ControlPanel.h"
#import "JavascriptProject.h"
#import "SourceTextView.h"
#import "ControlRegistry.h"
#import "jvm.h"

int lineToPos(NSString *str, int line);
extern int lastErrorLine;
extern char *lastErrorMessage;

@implementation JavascriptForm

-(NSString *) documentFile {
	NSURL *docURL = [self fileURL];
	if (!docURL)
		return nil;

	if (![docURL isFileURL])
		return nil;
	NSString *docPath = [docURL path];
	return [docPath lastPathComponent];
}

-(void)awakeFromNib {
	[objectPopup addItemWithTitle:@"Window"];
	[eventPopup addItemWithTitle:@"Load"];
	
	objectPopupReferences = [NSMutableArray new];
	FormEditor *editor = [formview getFormEditor];
	[editor setDocument:self];
	if (saved != nil) {
		[editor loadForm:saved];
		[editor postLoad];
	}
	
	[self rebuildObjectPopup];
	[self reloadEventPopup];
	[self updateCurrentEvent];
		
	[self updateEditEnabled];
	
//	[editor postLoad];
	
/*	
	NSString *source = [editor sourceForObject: nil forEvent: @"Load"];
	if (source)
		[textview setString:source];
	currentEditorControl = nil;
	currentEditorEvent = @"Load";
 */
}

-(void)makeWindowControllers {
	NSWindowController *ctl = [[NSWindowController alloc] initWithWindowNibName:@"FormDocument" owner:self];
	[self addWindowController:ctl];
	
	NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];
	NSString *docFile = [self documentFile];
	NSRect f;
	JavascriptProject *project = [JavascriptProject projectForDocument: self];
	if (docFile != nil && [project savedFrame: &f forWindow:docFile]) {
		[window setFrame: f display:YES];
	}
}

- (NSString *)windowNibName {
    return @"FormDocument";
}

-(NSArray *) autocompletesForThis {
	NSMutableArray *autocompletes = [[NSMutableArray new] autorelease];
	FormEditor *editor = [self editor];
	
	NSArray *controls = [editor controls];
	for (int idx = 0; idx < [controls count]; idx++) {
		FormControl *ctl = [controls objectAtIndex:idx];
		[autocompletes addObject:[ctl controlName]];
	}
	
	[autocompletes addObject:@"move("];
	[autocompletes addObject:@"showSheet("];
	[autocompletes addObject:@"alert("];
	[autocompletes addObject:@"show("];
	[autocompletes addObject:@"hide("];
	[autocompletes addObject:@"close("];

	[autocompletes addObject:@"left"];
	[autocompletes addObject:@"top"];
	[autocompletes addObject:@"width"];
	[autocompletes addObject:@"height"];
	[autocompletes addObject:@"innerWidth"];
	[autocompletes addObject:@"innerHeight"];

	return autocompletes;
}

-(NSArray *) autocompletesForMember: (NSString *) name {
	FormEditor *editor = [self editor];
	
	NSArray *controls = [editor controls];
	for (int idx = 0; idx < [controls count]; idx++) {
		FormControl *ctl = [controls objectAtIndex:idx];
		if ([name isEqualToString: [ctl controlName]]) {
			return [ctl autocompletes];
		}
	}
	return nil;
}

-(FormEditor *) editor {
	return [formview getFormEditor];
}

- (void)windowDidBecomeMain:(NSNotification *)notification {
	FormEditor *editor = [formview getFormEditor];

	[[ControlPanel sharedPanel] showForForm:self];
	[editor updateControlPanel];
}

- (void)windowWillClose:(NSNotification *)notification {
	NSWindow *wnd = [notification object];
	if (![wnd isDocumentEdited]) {
		[textview saveBreakpoints];
	}
}

-(void) saveBreakpoints {
	[textview saveBreakpoints];
}

- (void)windowDidResignMain:(NSNotification *)notification {
	[[ControlPanel sharedPanel] hide];
}

- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError {
	[textview saveBreakpoints];
	return [super writeToURL:absoluteURL ofType:typeName error:outError];
}

-(void) saveWindowFrame: (NSWindow *) window {
	NSString *docFile = [self documentFile];
	
	JavascriptProject *project = [JavascriptProject projectForDocument: self];
	if (project) {
		[project saveFrame:[window frame] forWindow: docFile];
	}
}

-(void)windowDidMove:(NSNotification *)notification {
	[self saveWindowFrame: [notification object]];
}

-(void)windowDidResize:(NSNotification *)notification {
	[self saveWindowFrame: [notification object]];
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
	FormEditor *editor = [formview getFormEditor];

	NSDictionary *save = [editor saveForm];
	
	return [NSPropertyListSerialization dataFromPropertyList:save format:NSPropertyListXMLFormat_v1_0 errorDescription:nil];
}

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
	NSPropertyListFormat fmt;

	saved = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:&fmt errorDescription:nil];
    [saved retain];
    return YES;
}

-(void)commitTextView {
	if (currentEditorEvent != nil) {
		NSString *content =  [NSString stringWithString: [textview string]];
		FormEditor *editor = [formview getFormEditor];
		[editor commitSource: content forObject: currentEditorControl forEvent: currentEditorEvent];
	}
}

-(void) switchCurrentEventAction: (NSArray *) param {
	FormEditor *editor = [formview getFormEditor];

	NSString *eventName = [param objectAtIndex:0];
	FormControl *ctl = nil;
	if ([param count] > 1) {
		ctl = [param objectAtIndex:1];
	}

	[self selectPopupControl:ctl];
	[self selectEvent: eventName];
	
	[editor selectControl:ctl];
	
	[self updateCurrentEvent];
}

-(void)editEvent: (NSString *) eventName forObject: (FormControl*) ctl {
	FormEditor *editor = [formview getFormEditor];

	if ([eventName isEqualToString:currentEditorEvent] && ctl == currentEditorControl)
		return;

	if (currentEditorEvent) {
		NSUndoManager *undoMgr = [self undoManager];
		if ([undoMgr canRedo] || [undoMgr canUndo]) {
			[undoMgr registerUndoWithTarget:self selector:@selector(switchCurrentEventAction:) object:[NSArray arrayWithObjects:currentEditorEvent, currentEditorControl, nil]];
		}
	}
	
	[self commitTextView];
	currentEditorEvent = eventName;
	currentEditorControl = ctl;
	NSString *source = [editor sourceForObject: ctl forEvent: eventName];
	if (source) {
		[textview setString: source];
	} else {
		[textview setString: @""];
	}
	
	NSString *ctlName;
	if (ctl)
		ctlName = [ctl controlName];
	else
		ctlName = @"_form";
	
	[textview setDocumentName: [NSString stringWithFormat:@"%@/%@/%@", [self documentFile], ctlName, eventName, nil]];
	[textview reloadBreakpoints];
}

-(void)updateCurrentEvent {
	[self editEvent: [self selectedEvent] forObject: [self selectedPopupControl]];
}

-(void)propChange: (NSArray *)data {
	id object = [data objectAtIndex:0];
	id val = [data objectAtIndex: 1];
	NSString *key = [data objectAtIndex: 2];
	FormEditor *editor = [self editor];
	
	id oldVal;
	
	if (object == [NSNull null])
		oldVal = [editor valueForProperty: key];
	else
		oldVal = [object valueForProperty: key];
	
	NSArray *undo = [NSArray arrayWithObjects: object, oldVal, key, nil];
	
	if (object == [NSNull null])
		[editor setValue: val forProperty:key];
	else
		[object setValue: val forProperty:key];
		
	[[self undoManager] registerUndoWithTarget:self selector:@selector(actionPropChange:) object:undo];
}

-(void)actionPropChange: (NSArray *)data {
	id object = [data objectAtIndex:0];
	id val = [data objectAtIndex: 1];
	NSString *key = [data objectAtIndex: 2];

	[self propChange: data];

	if (object == [NSNull null])
		[ControlPanel updateValue:val forProperty:key forControl:nil];
	else
		[ControlPanel updateValue:val forProperty:key forControl:object];
}

-(void)getInfo: (id) sender {
	FormEditor *editor = [formview getFormEditor];
	[editor showInfo];
}

-(void)rebuildObjectPopup {
	FormControl *currentSelection = [self selectedPopupControl];
	FormEditor *editor = [formview getFormEditor];
	NSMenu *newPopup = [NSMenu new];
	[newPopup setAutoenablesItems:NO];
	NSMenuItem *item = nil;
	item = [newPopup addItemWithTitle:@"Form" action:nil keyEquivalent:@""];
/*
	NSDictionary *attrsDictionary =
	[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:NSUnderlineStyleSingle]
								forKey:NSUnderlineStyleAttributeName];
	NSAttributedString *attrString =
    [[NSAttributedString alloc] initWithString:@"Form"
									attributes:attrsDictionary];
	[item setAttributedTitle:attrString];
	*/
	NSArray *controls = [editor controls];
	[objectPopupReferences removeAllObjects];

	for (int idx = 0; idx < [controls count]; idx++) {
		FormControl *ctl = [controls objectAtIndex:idx];
		NSString *name = [ctl controlName];
		if (name) {
			[newPopup addItemWithTitle:name action:nil keyEquivalent:@""];
			[objectPopupReferences addObject:ctl];
		}
	}

	[objectPopup setMenu: newPopup];
	[self selectPopupControl:currentSelection];
}

-(NSString *) selectedEvent {
	return [[eventPopup selectedItem] title];
}

-(void)reloadEventPopup {
	FormEditor *editor = [formview getFormEditor];
	FormControl * ctl = [self selectedPopupControl];
	NSArray *events;
	
	NSString *selectedEvent = [[eventPopup selectedItem] title];
	
	if (ctl)
		events = [ctl getEvents];
	else
		events = [editor getFormEvents];
	
	NSMenu *newEventMenu = [NSMenu new];

	int selectedIndex = 0;
	
	for (int idx = 0; idx < [events count]; idx++) {
		NSString *eventName = [events objectAtIndex: idx];
		[newEventMenu addItemWithTitle:eventName action:nil keyEquivalent:@""];
		if ([eventName isEqualToString:selectedEvent])
			selectedIndex = idx;
	}
	
	[eventPopup setMenu: newEventMenu];
	[eventPopup selectItem: [newEventMenu itemAtIndex:selectedIndex]];
}

-(void)selectedObjectChanged: (id) sender {
	FormEditor *editor = [formview getFormEditor];
	FormControl *selected = [self selectedPopupControl];
	
	[editor selectControl:selected];
	[self reloadEventPopup];
	[self updateCurrentEvent];
}

-(void)selectedEventChanged: (id) sender {
	[self updateCurrentEvent];
}

-(FormControl *) selectedPopupControl {
	NSMenuItem *item = [objectPopup selectedItem];
	int idx = [[objectPopup menu] indexOfItem:item];
	
	if (idx == 0)
		return nil;
	idx--;
	if (idx < [objectPopupReferences count])
		return [objectPopupReferences objectAtIndex:idx];
	return nil;
}

-(void)selectPopupControl: (FormControl *)ctl {
	NSMenu *menu = [objectPopup menu];
	if (ctl == nil) {
		[objectPopup selectItem:[menu itemAtIndex:0]];
	} else {
		int idx = [objectPopupReferences indexOfObject:ctl];
		if (idx < 0)
			return;
		
		[objectPopup selectItem:[menu itemAtIndex:idx + 1]];
	}
	[self reloadEventPopup];
}

-(void)editorSelectedControl:(FormControl *) ctl {
	[self selectPopupControl:ctl];
	[self updateCurrentEvent];
}

-(void)selectEvent: (NSString *) eventName {
	NSMenu *menu = [eventPopup menu];
	for (int idx = 0; idx < [menu numberOfItems]; idx++) {
		NSMenuItem *item = [menu itemAtIndex:idx];
		if ([eventName isEqualToString: [item title]]) {
			[eventPopup selectItem: item];
		}
	}
}

-(void) errorSheetClose:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo {
}

-(void) showError: (NSString *) message withHeader: (NSString *) header {
	NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];
	if (message) {
		NSAlert *alert = [[NSAlert alloc] init];
		[alert setMessageText:header];
		[alert setInformativeText:message];
		[alert addButtonWithTitle:@"Close"];
		[alert setAlertStyle:NSWarningAlertStyle];
		
		[alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(errorSheetClose:returnCode:contextInfo:) contextInfo:nil];
	}
}

-(void) showError: (NSString *) message atLine: (int) lineno  forScript: (NSString *) filename {
	NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];
	FormEditor *editor = [formview getFormEditor];

	NSString *docName = [self documentFile];
	NSString *f = [filename substringFromIndex:[docName length] + 1];
	NSArray *bits = [f componentsSeparatedByString:@"/"];
	NSString *controlName = [bits objectAtIndex:0];
	NSString *eventName = [bits objectAtIndex:1];
	
	FormControl *ctl = nil;
	if ([controlName isEqualToString:@"_form"]) {
		ctl = nil;
	} else {
		ctl = [editor controlByName: controlName];
	}
	
	[editor selectControl:ctl];
	[self selectPopupControl:ctl];
	[self selectEvent:eventName];
	[self updateCurrentEvent];

	if (lineno) {
		int pos = lineToPos([textview string], lineno);
		[textview setSelectedRange:NSMakeRange(pos, 0)];
		[textview scrollRangeToVisible:NSMakeRange(pos, 0)];
		[textview setRunMarker: lineno];

	}
	
	if (message) {
		NSAlert *alert = [[NSAlert alloc] init];
		[alert setMessageText:lineno ? [NSString stringWithFormat:@"Error at line %d", lineno] : @"Error"];
		[alert setInformativeText:message];
		[alert addButtonWithTitle:@"Close"];
		[alert setAlertStyle:NSWarningAlertStyle];
		
		[alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(errorSheetClose:returnCode:contextInfo:) contextInfo:nil];
	}
}

-(void) updateEditEnabled {
	JavascriptProject *project = [JavascriptProject projectForDocument: self];
	[textview setEditable:project == nil || ![project isDebugging]];
}

-(void) clearStop {
	[textview setRunMarker:0];
}

-(void) setRunMaker:(int) line {
	[textview setRunMarker:line];
}

-(FormControl *) controlByName: (NSString *) controlName {
	FormEditor *editor = [formview getFormEditor];

	return [editor controlByName:controlName];
}
	
	
-(void)showError: (NSString *) message atLine: (int) nLine {
	NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];

	if (nLine > 0) {
		int pos = lineToPos([textview string], nLine);
		[textview setSelectedRange:NSMakeRange(pos, 0)];
		[textview scrollRangeToVisible:NSMakeRange(pos, 0)];
		[textview setRunMarker: nLine];
		
	}
	
	if (message) {
		NSAlert *alert = [[NSAlert alloc] init];
		[alert setMessageText:nLine ? [NSString stringWithFormat:@"Error at line %d", nLine] : @"Error"];
		[alert setInformativeText:message];
		[alert addButtonWithTitle:@"Close"];
		[alert setAlertStyle:NSWarningAlertStyle];
		
		[alert beginSheetModalForWindow:window modalDelegate:self didEndSelector:@selector(errorSheetClose:returnCode:contextInfo:) contextInfo:nil];
	}
}

+(bool)validateHandler: (NSString *) script forControl: (NSString *) ctlName forEvent: (NSString *) eventName forPath: (NSString *) path forProject: (JavascriptProject *) project {
	if (!script)
		return true;
	return [[jvm instance] verifyJavascript:script forSource:path forControl:ctlName forEvent:eventName forProject: project ];
	/*
	const char *strScript = [script UTF8String];
	JSFunction *compiledScript = JS_CompileFunction( cx, nil, nil, 0, nil, strScript, strlen(strScript), "source", 1);
	if (!compiledScript) {
		NSURL *docURL = [NSURL fileURLWithPath:path];
		JavascriptForm *sourceDoc = [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:docURL display:YES error: nil];
		if (ctlName)
			[sourceDoc selectPopupControl:[sourceDoc controlByName: ctlName]];
		else
			[sourceDoc selectPopupControl:nil];
		[sourceDoc selectEvent:eventName];
		[sourceDoc updateCurrentEvent];
		[sourceDoc showError:[NSString stringWithCString:lastErrorMessage] atLine:lastErrorLine];
		return false;
	}
	return true;
	 */
}

+(bool)precompileForm: (NSString *) path {
	return YES;
}

+(bool)verifyForm: (NSString *) path forProject: (JavascriptProject *) project {
	NSPropertyListFormat fmt;

	NSData *data = [NSData dataWithContentsOfFile:path];
	if (!data)
		return true;
	NSDictionary *form = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:&fmt errorDescription:nil];
	if (!form)
		return true;
	
	NSArray *formEvents = [FormEditor getFormEvents];
	for (int idx = 0; idx < [formEvents count]; idx++) {
		NSString *eventName = [formEvents objectAtIndex:idx];
		if (![JavascriptForm validateHandler: [form objectForKey:eventName] forControl: nil forEvent: eventName forPath: path forProject:project])
			return false;
	}
	
	ControlRegistry *registry = [ControlRegistry instance];
	
	NSArray *controls = [form objectForKey:@"controls"];
	for (int idx = 0; idx < [controls count]; idx++) {
		NSDictionary *defn = [controls objectAtIndex:idx];
		NSString *ctlType = [defn objectForKey:@"type"];
		NSString *ctlName = [defn objectForKey:@"name"];
		FormControl *ctl = [registry controlForName:ctlType];
		if (ctl ) {
			[ctl setControlType:ctlType];
			
			NSArray *ctlEvents = [ctl getEvents];
			
			for (int eventIdx = 0; eventIdx < [ctlEvents count]; eventIdx++) {
				NSString *eventName = [ctlEvents objectAtIndex:eventIdx];
				if (![JavascriptForm validateHandler: [defn objectForKey:eventName] forControl: ctlName forEvent: eventName forPath: path forProject:project])
					return false;
			}
			
			[ctl release];
		}
	}
	
	return true;
}

+(bool)compressEvent: (NSMutableDictionary *) container forEvent: (NSString *) eventName {
	NSString *handler = [container objectForKey:eventName];
	if (!handler)
		return true;
	NSString *compressed = [[jvm instance] compressJavascript:handler];
	if (compressed) {
		[container setObject:compressed forKey:eventName];
	}
	return true;
}

+(bool)optimiseForm: (NSMutableDictionary *) form {	
	NSArray *formEvents = [FormEditor getFormEvents];
	for (int idx = 0; idx < [formEvents count]; idx++) {
		NSString *eventName = [formEvents objectAtIndex:idx];
		if (![JavascriptForm compressEvent: form forEvent: eventName])
			return false;
	}
	
	ControlRegistry *registry = [ControlRegistry instance];
	
	NSArray *controls = [form objectForKey:@"controls"];
	for (int idx = 0; idx < [controls count]; idx++) {
		NSDictionary *defn = [controls objectAtIndex:idx];
		NSString *ctlType = [defn objectForKey:@"type"];
		NSString *ctlName = [defn objectForKey:@"name"];
		FormControl *ctl = [registry controlForName:ctlType];
		if (ctl ) {
			[ctl setControlType:ctlType];
			
			NSArray *ctlEvents = [ctl getEvents];
			
			for (int eventIdx = 0; eventIdx < [ctlEvents count]; eventIdx++) {
				NSString *eventName = [ctlEvents objectAtIndex:eventIdx];
				if (![JavascriptForm compressEvent: defn forEvent: eventName])
					return false;
			}
			
			
			[ctl release];
		}
	}
	
	return true;
}

@end
